/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cardgame.game.bots;

import java.util.LinkedList;
import cardgame.Util;
import cardgame.game.*;
import cardgame.game.commands.*;
import cardgame.game.monsters.spells.parameter.*;

/**
 *
 * @author Carl
 */
public class IntelligentBot extends LocalGameBot{
    
    private int playerIndex;
    
    @Override
    public void performTurn(){
        super.performTurn();
        playerIndex = localGame.getCurrentPlayerIndex();
        LinkedList<Command> bestCommands = getBestCommands(new LinkedList<Command>(), 0);
        for(Command command : bestCommands){
            executeCommand(command);
        }
    }
    
    private LinkedList<Command> getBestCommands(LinkedList<Command> previousCommands, int depth){
        LinkedList<Command> validCommands = getValidCommands();
        LinkedList<Command> bestCommands = previousCommands;
        int bestCommandsEvaluation = Integer.MIN_VALUE;
        while(!validCommands.isEmpty()){
            Command command = validCommands.pop();
            doAction(command);
            LinkedList<Command> newPreviousCommands = new LinkedList<Command>(previousCommands);
            newPreviousCommands.add(command);
            LinkedList<Command> commands = getBestCommands(newPreviousCommands, depth + 1);
            int evaluation = getGameEvaluation();
            if(evaluation > bestCommandsEvaluation){
                bestCommands = commands;
                bestCommandsEvaluation = evaluation;
            }
            undoAction();
        }
        return bestCommands;
    }
    
    private LinkedList<Command> getValidCommands(){
        LinkedList<Command> commands = new LinkedList<Command>();
        if(localGame.getCurrentPlayerIndex() == playerIndex){
            Player player = localGame.getPlayer(playerIndex);
            Hand hand = player.getHand();
            for(int i=0;i<hand.size();i++){
                Card card = hand.get(i);
                if(card instanceof ManaProvider){
                    if(player.canAddMana()){
                        commands.add(new AddManaCommand(i));
                    }
                }
                else if(card instanceof MonsterCard){
                    MonsterCard monsterCard = (MonsterCard) card;
                    if(player.canPay(monsterCard.getSummonCost(), monsterCard)){
                        commands.add(new SummonMonsterCommand(i, getManaIndices(monsterCard.getSummonCost().getMana())));
                    }
                }
                else if(card instanceof SpellCard){
                    SpellCard spellCard = (SpellCard) card;
                    Spell spell = spellCard.getSpell();
                    if(player.canCast(spell)){
                        int[] cost_manaIndices = getManaIndices(spell.getCost().getMana());
                        LinkedList<SpellParameter[]> spellParametersList = new LinkedList<SpellParameter[]>();
                        generateSpellParameters(spell, spellParametersList, new SpellParameter[spell.getSpellParameterFormat().size()]);
                        for(SpellParameter[] spellParameters : spellParametersList){
                            commands.add(new CastSpellCommand(i, cost_manaIndices, spellParameters));
                        }
                    }
                }
            }
            Cards monsters = player.getMonsters();
            Cards enemyMonsters = player.getEnemy().getMonsters();
            for(int i=0;i<monsters.size();i++){
                MonsterCard monsterCard = (MonsterCard) monsters.get(i);
                if(monsterCard.canAttack()){
                    if(enemyMonsters.isEmpty()){
                        commands.add(new AttackPlayerCommand(i));
                    }
                    else{
                        for(int r=0;r<enemyMonsters.size();r++){
                            commands.add(new AttackMonsterCommand(i, r));
                        }
                    }
                }
                for(int r=0;r<monsterCard.getSpells().length;r++){
                    Spell spell = monsterCard.getSpells()[r];
                    if(player.canPay(spell.getCost(), monsterCard)){
                        int[] cost_manaIndices = getManaIndices(spell.getCost().getMana());
                        LinkedList<SpellParameter[]> spellParametersList = new LinkedList<SpellParameter[]>();
                        generateSpellParameters(spell, spellParametersList, new SpellParameter[spell.getSpellParameterFormat().size()]);
                        for(SpellParameter[] spellParameters : spellParametersList){
                            commands.add(new CastMonsterSpellCommand(i, r, cost_manaIndices, spellParameters));
                        }
                    }
                }
            }
            if(hand.size() > game.getGameMode().getMaximumHandCards(player)){
                for(int i=0;i<hand.size();i++){
                    commands.add(new ThrowingOffCommand(i));
                }
            }
            else{
                commands.add(new EndTurnCommand());
            }
        }
        return commands;
    }
    
    private void generateSpellParameters(Spell spell, LinkedList<SpellParameter[]> spellParametersList, SpellParameter[] constantParameters){
        boolean isConstant = true;
        for(int i=0;i<constantParameters.length;i++){
            if(constantParameters[i] == null){
                SpellParameterFormat_Entry entry = spell.getSpellParameterFormat().getEntry(i);
                if((entry.getType() == SpellParameterFormat_Entry.Type.ALL) || (entry.getType() == SpellParameterFormat_Entry.Type.HAND_CARD)){
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(0).getHand());
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(1).getHand());
                }
                if((entry.getType() == SpellParameterFormat_Entry.Type.ALL) || (entry.getType() == SpellParameterFormat_Entry.Type.MANA)){
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(0).getManaPool());
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(1).getManaPool());
                }
                if((entry.getType() == SpellParameterFormat_Entry.Type.ALL) || (entry.getType() == SpellParameterFormat_Entry.Type.MONSTER)){
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(0).getMonsters());
                    generateSpellParameter(spell, spellParametersList, constantParameters, i, localGame.getPlayer(1).getMonsters());
                }
                isConstant = false;
            }
        }
        if(isConstant){
            spellParametersList.add(constantParameters);
        }
    }
    
    private void generateSpellParameter(Spell spell, LinkedList<SpellParameter[]> spellParametersList, SpellParameter[] constantParameters, int parameterIndex, Cards targetCards){
        SpellParameterFormat_Entry entry = spell.getSpellParameterFormat().getEntry(parameterIndex);
        for(int i=0;i<targetCards.size();i++){
            Card targetCard = targetCards.get(i);
            if((spell.getCaster() instanceof SpellCard) && (targetCard == spell.getCaster())){
                continue;
            }
            if((entry.getTargetOwner() == SpellParameterFormat_Entry.Owner.ALL)
            || ((entry.getTargetOwner() == SpellParameterFormat_Entry.Owner.ALLIED) && (targetCard.getOwner() == spell.getCaster().getOwner()))
            || ((entry.getTargetOwner() == SpellParameterFormat_Entry.Owner.ENEMY) && (targetCard.getOwner() != spell.getCaster().getOwner()))){
                SpellParameter[] spellParameters = new SpellParameter[constantParameters.length];
                for(int r=0;r<parameterIndex;r++){
                    spellParameters[r] = constantParameters[r];
                }
                SpellParameter spellParameter;
                if(targetCards instanceof Hand){
                    spellParameter = new HandCardParameter(targetCard.getOwner().getIndex(), i);
                }
                else if(targetCards instanceof ManaPool){
                    spellParameter = new ManaParameter(targetCard.getOwner().getIndex(), i);
                }
                else{
                    spellParameter = new MonsterParameter(targetCard.getOwner().getIndex(), i);
                }
                spellParameters[parameterIndex] = spellParameter;
                generateSpellParameters(spell, spellParametersList, spellParameters);
            }
        }
    }
    
    private int getGameEvaluation(){
        Player player = localGame.getPlayer(playerIndex);
        if(player.getEnemy().getLifepoints() <= 0){
            return Integer.MAX_VALUE;
        }
        int evaluation = 0;
        evaluation += player.getManaPool().size();
        evaluation += getFieldEvaluation(player);
        evaluation -= getFieldEvaluation(player.getEnemy());
        evaluation /= player.getEnemy().getLifepoints();
        return evaluation;
    }
    
    private int getFieldEvaluation(Player player){
        int evaluation = 0;
        evaluation += player.getManaPool().size();
        for(int i=0;i<player.getMonsters().size();i++){
            MonsterCard monsterCard = (MonsterCard) player.getMonsters().get(i);
            evaluation += (monsterCard.getAttackDamage() + monsterCard.getCurrentLifepoints());
        }
        return evaluation;
    }
    
    private int[] getManaIndices(ManaAmount manaAmount){
        LinkedList<Integer> manaIndices = new LinkedList<Integer>();
        ManaPool manaPool = localGame.getPlayer(localGame.getCurrentPlayerIndex()).getManaPool();
        for(Mana mana : Mana.values()){
            if(mana != Mana.CUSTOM){
                int remainingAmount = manaAmount.getMana(mana);
                for(int i=0;i<manaPool.size();i++){
                    ManaProvider manaProvider = (ManaProvider) manaPool.get(i);
                    if(manaProvider.getProvidedMana().getMana(mana) == 1){
                        if(!manaIndices.contains(i)){
                            manaIndices.add(i);
                            remainingAmount--;
                            if(remainingAmount <= 0){
                                break;
                            }
                        }
                    }
                }
            }
        }
        int remainingCustomAmount = manaAmount.getMana(Mana.CUSTOM);
        for(int i=0;i<manaPool.size();i++){
            ManaProvider manaProvider = (ManaProvider) manaPool.get(i);
            int providedAmount = manaProvider.getProvidedMana().getAmount();
            if((!manaIndices.contains(i)) && (providedAmount <= remainingCustomAmount)){
                manaIndices.add(i);
                remainingCustomAmount -= providedAmount;
                if(remainingCustomAmount <= 0){
                    break;
                }
            }
        }
        return Util.convertToArray(manaIndices);
    }
}
